import { ConstraintType, EventsRes } from '@amcax/base';
import { ctx2 } from '@app-cad/module';
import {
  calculatePoint,
  distanceToLine,
  getDotProduct,
  getSymmetricPoint,
  isEquals,
} from '@app-cad/common/utils';
import {
  SKETCH_CENTEREVENTS,
  sketchEventCenter,
} from '@app-cad/environments/nextcad/events/SketchEventCenter';
import { wsApi } from '@app-cad/server';
import { Vector2, Vector2tuple, Vector3 } from 'three-legacy';
import CmdBase, { PickType, ReturnDataInfo } from '../../CmdBase';
import {
  MouseState,
  rectangle,
  Rectangle,
  RectangleRelation,
  State,
  TrackingData,
} from './types';
import { Dimension } from '../dimension/Dimension';
let dimension1: Dimension = null;
let dimension2: Dimension = null;
type SnapType = {
  curveId: string;
  pickType: PickType;
  point?: Vector3;
};
export default class CmdInteractor extends CmdBase {
  _constraint: Rectangle;
  trackingData: TrackingData = {
    vertexs: [],
  };
  // 多边形中心点
  private startPoint: Vector3;
  // 第二个点
  private secondPoint: Vector3;
  // 最后一个点
  private endPoint: Vector3; // 矩形终点
  // 根据前三个点生成的点
  private generatedPoint: Vector3;
  private currentState: MouseState = State.NONE;
  firstDimLen: number = null;

  private firstSnapInfo: SnapType;
  private secondSnapInfo: SnapType;
  private endSnapInfo: SnapType;

  private isClockwise: boolean = false;
  dimension1: Dimension = null;
  dimension2: Dimension = null;

  constructor(rectangleRelation: RectangleRelation, name: string) {
    super();
    this._constraint = { rectangleRelation: rectangleRelation };
    this.setState(name);
    this.registerEvents();
    dimension1 = new Dimension();
    dimension2 = new Dimension();
  }

  enterKeyDown = () => {
    if (this.currentState !== State.DRAW) {
      return;
    }
    if (this.isExist([1, 3])) {
      if (!this.firstDimLen) {
        const flag = dimension1.determineDim();
        if (flag) {
          this.preview();
          dimension2.focus();
        }
      } else {
        const flag = dimension2.determineDim();
        if (flag) {
          this.preview();
          this.finish();
        }
      }
    } else {
      if (dimension1.dimType === 'preview') {
        const flag = dimension1.determineDim();
        if (flag) {
          this.setFirstLine();
          this.drawAssist();
          this.createUpdateLine([this.startPoint, this.endPoint], 'rline', 1);
        }
      }
      if (dimension2?.dimType === 'preview') {
        const flag = dimension2.determineDim();
        if (flag) {
          this.setFirstLine();
          this.preview();
          if (this.isExist([4, 5])) {
            this.finish();
          }
        }
      }
    }
  };
  // 平行四边形和三点拐角矩形
  setFirstLine() {
    if (this.isExist([1, 3])) {
      return;
    }
    if (dimension1.dimType === 'create' && !this.secondPoint) {
      const vec = new Vector3()
        .subVectors(this.endPoint, this.startPoint)
        .normalize();
      this.endPoint = calculatePoint(
        vec,
        dimension1.getDimInfo().value,
        this.startPoint,
      );
    }
    if (dimension2?.dimType === 'create') {
      if (this.isExist([5])) {
        const p2 = this.trackingData.vertexs[0];
        const vec = new Vector3().subVectors(p2, this.secondPoint).normalize();
        this.endPoint = calculatePoint(
          vec,
          dimension2.getDimInfo().value / 2,
          this.secondPoint,
        );
      } else if (this.isExist([4])) {
        const p2 = this.trackingData.vertexs[2];
        const vec = new Vector3().subVectors(p2, this.secondPoint).normalize();
        this.endPoint = calculatePoint(
          vec,
          dimension2.getDimInfo().value,
          this.secondPoint,
        );
      } else if (this.isExist([2])) {
        const [p2] = this.trackingData.vertexs.slice(1, 3);
        const vec = new Vector3().subVectors(this.mouseWV, p2).normalize();
        this.endPoint = calculatePoint(vec, dimension2.getDimInfo().value, p2);
      }
    }
  }
  reset() {
    this.currentState = State.NONE;
    this.startPoint = null;
    this.secondPoint = null;
    this.generatedPoint = null;
    this.endPoint = null;
    this.assistPoints = [];
    this.firstDimLen = null;
    this.firstSnapInfo = null;
    this.endSnapInfo = null;
    this.trackingData = {
      vertexs: [],
    };
    this.removeTempPoint();
  }

  setState(name: string) {
    sketchEventCenter().setState(name);
  }

  registerEvents() {
    sketchEventCenter()
      .on(SKETCH_CENTEREVENTS.POINTERMOVE, this.onMouseMove)
      .on(SKETCH_CENTEREVENTS.POINTERDOWN, this.onMouseDown)
      .on(SKETCH_CENTEREVENTS.KEYUP, this.onKeyUp)
      .addExit(this.destroy.bind(this));
  }

  unregisterEvents() {
    sketchEventCenter()
      .setDefault()
      .off(SKETCH_CENTEREVENTS.POINTERMOVE, this.onMouseMove)
      .off(SKETCH_CENTEREVENTS.POINTERDOWN, this.onMouseDown)
      .off(SKETCH_CENTEREVENTS.KEYUP, this.onKeyUp);
  }

  releaseObjects() {
    dimension1?.destroy();
    dimension2?.destroy();
    this.clearLine();
    this.assisGroup.clear();
    ctx2.viewer.requestRender();
  }

  onMouseMove = (e: EventsRes) => {
    const noSnap = this.isExist([4, 5]) && this.secondPoint;
    this.setMouseWV(e, !noSnap, !noSnap);
    if (this.currentState === State.DRAW) {
      if (
        this.startPoint.x === this.mouseWV.x &&
        this.startPoint.y === this.mouseWV.y &&
        this.trackingData.vertexs.length === 5
      ) {
        return;
      }
      this.endPoint = this.mouseWV;
      if (this.secondPoint || this.isExist([1, 3])) {
        this.setFirstLine();
        this.preview();
      } else {
        this.setFirstLine();
        this.createUpdateLine([this.startPoint, this.endPoint], 'rline', 1);
        this.drawAssist();
      }
    }
  };
  onMouseDown = (e: EventsRes) => {
    if (e.button.onLeft) {
      // 鼠标左键，进行绘制
      if (this.currentState === State.NONE) {
        this.startPoint = this.mouseWV;
        if (!this.generatedPoint) {
          this.generatedPoint = this.startPoint;
        }
        if (this.pickType !== PickType.Null) {
          this.firstSnapInfo = {
            pickType: this.pickType,
            curveId: this.pickSnapInfo.id,
            point: this.mouseWV.clone(),
          };
        }
        this.currentState = State.DRAW;
        const flag = this.isExist([2, 4, 5]);
        // 添加辅助点，用来做定点捕捉
        this.createAssistPoint(this.startPoint, null, null, flag);
        this.preview();
      } else if (this.currentState === State.DRAW) {
        if (this.isExist([2, 4, 5]) && !this.secondPoint) {
          this.clearLine(['rline']);
          if (this.pickType !== PickType.Null) {
            this.secondSnapInfo = {
              pickType: this.pickType,
              curveId: this.pickSnapInfo.id,
              point: this.mouseWV.clone(),
            };
          }
          this.secondPoint = this.endPoint;
          if (dimension1.dimType === 'preview') {
            dimension1.destroy();
          }
        } else {
          if (this.pickType !== PickType.Null) {
            this.endSnapInfo = {
              pickType: this.pickType,
              curveId: this.pickSnapInfo?.id,
              point: this.mouseWV.clone(),
            };
          }
          this.finish();
          this.currentState = State.FINISH;
        }
      }
    } else if (e.button.onRight && !this.closeRightClick) {
      this.reset();
      this.releaseObjects();
    }
  };

  // 更新点的信息
  updateVertexs() {
    if (!this.endPoint) {
      return;
    }
    this.getIsClockwise();
    switch (this._constraint.rectangleRelation) {
      case RectangleRelation.DIAGONAL:
        this.getDiagonalVertexs();
        break;
      case RectangleRelation.PARALLEL:
        this.getParallelRectanglePoints();
        break;
      case RectangleRelation.CENTER:
        this.getCenterRectanglePoints();
        break;
      case RectangleRelation.THREE_POINT:
        this.getThreePointRectanglePoints();
        break;
      case RectangleRelation.THREE_POINT_CENTER:
        this.getThreePointCenterRectanglePoints();
        break;
      default:
        break;
    }
  }
  // 判断是否存在
  isExist(types: RectangleRelation[]) {
    const rectangleType = this._constraint.rectangleRelation;
    return types.includes(rectangleType);
  }
  // 顺时针判断
  getIsClockwise() {
    if (this.isExist([2, 4, 5])) {
      this.isClockwise = this.getPointPosition(
        this.startPoint,
        this.secondPoint,
        this.mouseWV,
      );
    } else if (this.isExist([3])) {
      const secP = this.startPoint.clone();
      secP.x = this.endPoint.x * 2;
      this.isClockwise = this.getPointPosition(
        this.startPoint,
        secP,
        this.mouseWV,
      );
    }
    return this.isClockwise;
  }
  // 获取三点中心矩形的四个点
  getThreePointCenterRectanglePoints() {
    const vec = new Vector3()
      .subVectors(this.secondPoint, this.startPoint)
      .normalize();
    // 垂直向量
    const vecVer = new Vector3(vec.y, -vec.x, 0);
    const sign =
      getDotProduct(this.secondPoint, this.mouseWV, vecVer) >= 0 ? 1 : -1;
    const length = distanceToLine(
      new Vector2(this.endPoint.x, this.endPoint.y),
      {
        start: new Vector2(this.startPoint.x, this.startPoint.y),
        vec: new Vector2(vec.x, vec.y),
        normal: new Vector2(-vec.y, vec.x),
      },
    );
    const p3 = calculatePoint(vecVer, length * sign, this.secondPoint);
    const temp4 = calculatePoint(vecVer, length * sign, this.startPoint);
    const p4sy = getSymmetricPoint(temp4, this.startPoint);
    const secsy = getSymmetricPoint(this.secondPoint, this.startPoint);
    const p4 = getSymmetricPoint(p3, this.secondPoint);
    const p2 = getSymmetricPoint(p3, temp4);
    const p1 = getSymmetricPoint(p4, p4sy);
    if (this.isClockwise) {
      this.trackingData.vertexs = [p3, p2, p1, p4, p3];
    } else {
      this.trackingData.vertexs = [p3, p4, p1, p2, p3];
    }
    this.endPoint = p3;
    this.createUpdateLine([p4sy, temp4], 'xline', 1);
    this.createUpdateLine([this.secondPoint, secsy], 'yline', 1);
  }
  // 获取对角矩形的四个点
  getDiagonalVertexs() {
    let offsetx = this.endPoint.x - this.startPoint.x;
    let offsety = this.endPoint.y - this.startPoint.y;
    const signx = Math.sign(offsetx);
    const signy = Math.sign(offsety);
    if (dimension2.dimType === 'create') {
      offsetx = dimension2.getDimInfo()?.value * signx || offsetx;
    }
    if (dimension1.dimType === 'create') {
      offsety = dimension1.getDimInfo()?.value * signy || offsety;
      this.firstDimLen = offsety;
    }
    const p1 = { x: this.startPoint.x, y: this.startPoint.y };
    const p2 = { x: p1.x, y: p1.y + offsety };
    const p3 = { x: p1.x + offsetx, y: p1.y + offsety };
    const p4 = { x: p1.x + offsetx, y: p1.y };
    const xMax = Math.max(p1.x, p2.x, p3.x, p4.x);
    const xMin = Math.min(p1.x, p2.x, p3.x, p4.x);
    const yMax = Math.max(p1.y, p2.y, p3.y, p4.y);
    const yMin = Math.min(p1.y, p2.y, p3.y, p4.y);
    this.trackingData.vertexs = [
      new Vector3(xMin, yMax, 0),
      new Vector3(xMin, yMin, 0),
      new Vector3(xMax, yMin, 0),
      new Vector3(xMax, yMax, 0),
      new Vector3(xMin, yMax, 0),
    ];
  }

  // 获取三点拐角矩形的点
  getThreePointRectanglePoints() {
    const vec = new Vector3()
      .subVectors(this.secondPoint, this.startPoint)
      .normalize();
    // 垂直向量
    const vecVer = new Vector3(vec.y, -vec.x, 0);
    const sign =
      getDotProduct(this.secondPoint, this.mouseWV, vecVer) >= 0 ? 1 : -1;
    const length = distanceToLine(
      new Vector2(this.endPoint.x, this.endPoint.y),
      {
        start: new Vector2(this.startPoint.x, this.startPoint.y),
        vec: new Vector2(vec.x, vec.y),
        normal: new Vector2(-vec.y, vec.x),
      },
    );
    const point3 = calculatePoint(vecVer, length * sign, this.secondPoint);
    const point4 = calculatePoint(vecVer, length * sign, this.startPoint);
    this.trackingData.vertexs = [
      this.startPoint,
      this.secondPoint,
      point3,
      point4,
      this.startPoint,
    ];
    this.endPoint = point3;
  }

  // 获取平行四边形的点
  getParallelRectanglePoints() {
    // 边长
    const length1 = this.secondPoint.distanceTo(this.startPoint);
    // 向量
    const vector1 = new Vector3(
      this.secondPoint.x - this.startPoint.x,
      this.secondPoint.y - this.startPoint.y,
      0,
    );
    this.generatedPoint = calculatePoint(vector1, -length1, this.endPoint);
    this.trackingData.vertexs = [
      this.startPoint,
      this.secondPoint,
      this.endPoint,
      this.generatedPoint,
      this.startPoint,
    ];
  }
  // 获取中心矩形的点
  getCenterRectanglePoints() {
    let offsetx = this.endPoint.x - this.startPoint.x;
    let offsety = this.endPoint.y - this.startPoint.y;
    const signx = Math.sign(offsetx);
    const signy = Math.sign(offsety);
    if (dimension2.dimType === 'create') {
      offsetx = (dimension2.getDimInfo()?.value / 2) * signx || offsetx;
    }
    if (dimension1.dimType === 'create') {
      offsety = (dimension1.getDimInfo()?.value / 2) * signy || offsety;
      this.firstDimLen = offsety;
    }
    const point1 = new Vector3(
      this.startPoint.x - offsetx,
      this.startPoint.y + offsety,
      0,
    );
    const point2 = new Vector3(
      this.startPoint.x + offsetx,
      this.startPoint.y + offsety,
      0,
    );
    const point3 = new Vector3(
      this.startPoint.x + offsetx,
      this.startPoint.y - offsety,
      0,
    );
    const point4 = new Vector3(
      this.startPoint.x - offsetx,
      this.startPoint.y - offsety,
      0,
    );
    this.trackingData.vertexs = [point4, point1, point2, point3, point4];
    const p1 = point1.clone().add(point2).divideScalar(2);
    const p2 = point3.clone().add(point4).divideScalar(2);
    const p3 = point1.clone().add(point4).divideScalar(2);
    const p4 = point2.clone().add(point3).divideScalar(2);
    this.createUpdateLine([p1, p2], 'xline', 1);
    this.createUpdateLine([p3, p4], 'yline', 1);
  }

  preview() {
    this.updateVertexs();
    if (!this.endPoint || this.trackingData.vertexs.length < 3) {
      return;
    }
    this.createUpdateLine(this.trackingData.vertexs);
    this.drawAssist();
  }
  drawAssist() {
    if (this.isExist([2, 4, 5])) {
      const secondPoint = this.secondPoint || this.endPoint;
      const dot = new Vector3()
        .subVectors(this.startPoint, secondPoint)
        .normalize()
        .dot(new Vector3(1, 0, 0));
      if (!this.secondPoint) {
        dimension1.dimLine([this.startPoint, secondPoint], dot > 0 ? 1 : -1);
      }
      if (this.secondPoint != null && this.endPoint != null) {
        const poins: [Vector3, Vector3] = [secondPoint, this.endPoint];
        if (this.isExist([5])) {
          poins[0] = getSymmetricPoint(this.endPoint, secondPoint);
        }
        const symbol = dot > 0 ? 1 : -1;
        if (this.isExist([5]) && this.isClockwise) {
          poins.reverse();
        }
        dimension2.dimLine(poins, symbol);
      }
    } else {
      const [p1, p2, p3] = this.trackingData.vertexs;
      const dot = new Vector3()
        .subVectors(p1, p2)
        .normalize()
        .dot(new Vector3(0, 1, 0));
      const dot2 = new Vector3()
        .subVectors(p2, p3)
        .normalize()
        .dot(new Vector3(1, 0, 0));
      dimension1.dimLine([p1, p2], dot * dot2 * -1 > 0 ? 1 : -1);
      dimension2.dimLine([p2, p3], dot * dot2 * -1 > 0 ? 1 : -1, false);
    }
    ctx2.viewer.requestRender();
  }
  finish = async () => {
    // 鼠标右键，完成
    await this.finishDraw();
    this.reset();
    this.releaseObjects();
  };

  isLoading = false;

  async finishDraw() {
    const param = this.getParams();
    if (!param) {
      return;
    }

    if (this.isLoading) {
      return;
    }

    try {
      this.isLoading = true;
      const rectangleData: ReturnDataInfo = await wsApi.createSketchRect(
        this.sketchId,
        param,
      );
      await this.render(
        rectangleData,
        param.annotations.some((a) => a != null),
      );
    } finally {
      this.isLoading = false;
    }
  }
  getParams() {
    const { points, p1, p2, p3 } = this.getPoints();
    if (points.length === 5) {
      points.pop();
      return {
        points,
        curveStyle: this.lineType,
        type: rectangle[this._constraint.rectangleRelation],
        constraints: this.addConstraint([...points], { p1, p2, p3 }),
        annotations: [dimension1.addDimension(), dimension2.addDimension()],
      };
    }
    return null;
  }
  addConstraint(ps: Vector2tuple[], { p1, p2, p3 }) {
    if (this.isExist([5, 3])) {
      ps.push([this.startPoint.x, this.startPoint.y]);
    }
    const constraints = [];
    const type = ConstraintType.PointOnObject;
    for (let i = 0; i < ps.length; i++) {
      if (!Array.isArray(ps[i])) {
        continue;
      }
      const p = { x: ps[i][0], y: ps[i][1] };
      const params = { curve: null, target: ps[i], type };
      if (isEquals(p, p1)) {
        Object.assign(params, this.getConsParam(this.firstSnapInfo, [5, 3]));
      } else if (isEquals(p, p2)) {
        Object.assign(params, this.getConsParam(this.secondSnapInfo, [5]));
      } else if (isEquals(p, p3)) {
        Object.assign(params, this.getConsParam(this.endSnapInfo));
      }
      params.curve && constraints.push(params);
    }
    return constraints;
  }
  getConsParam(snapInfo, isExist = []) {
    const { curveId, pickType } = snapInfo;
    if (pickType === PickType.Null) {
      return {};
    }
    const params: { [key: string]: any } = {};
    params.curve = curveId;
    if (this.isExist(isExist) && pickType === PickType.Point) {
      params.type = ConstraintType.Coincident;
    }
    return params;
  }
  getPoints() {
    const p1 =
      this.firstSnapInfo?.pickType === PickType.Point
        ? this.firstSnapInfo?.point
        : null;
    const p2 =
      this.secondSnapInfo?.pickType === PickType.Point
        ? this.secondSnapInfo?.point
        : null;
    const p3 =
      this.endSnapInfo?.pickType === PickType.Point
        ? this.endSnapInfo?.point
        : null;
    const points = new Array(this.trackingData.vertexs.length);
    for (let i = 0; i < this.trackingData.vertexs.length; i++) {
      const p = this.trackingData.vertexs[i];
      if (p1 && isEquals(p, p1)) {
        points[i] = this.firstSnapInfo.curveId;
      } else if (p2 && isEquals(p, p2)) {
        points[i] = this.secondSnapInfo.curveId;
      } else if (p3 && isEquals(p, p3)) {
        points[i] = this.endSnapInfo.curveId;
      } else {
        points[i] = [p.x, p.y];
      }
    }
    return {
      points,
      p1: this.firstSnapInfo?.point,
      p2: this.secondSnapInfo?.point,
      p3: this.endSnapInfo?.point,
    };
  }
}
